﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.Specialized;

namespace SQLToolbox.Types
{

	/// <summary>
	/// Class that represents a database script
	/// Wraps a StringCollection class
	/// </summary>
	public class Script : StringCollection, IEnumerable<String>
	{
		public Script()
		{
		
		}
		public Script(string s)
		{
			this.Add(s);
		}
		public Script(StringCollection col)
		{
			this.AddRange(col);
		}

		public void AddRange(StringCollection col)
		{
			foreach (string row in col)
				this.Add(row);
		}
		public string MergeForView()
		{
			if (this.Count > 0)
				return this.Cast<string>().Aggregate((outputString, next) => outputString + "\r\nGO\r\n" + next);
			else
				return string.Empty;
		}

		#region IEnumerable<string> Members

		public new IEnumerator<string> GetEnumerator()
		{
			for (var i = 0; i < this.Count; i++)
				yield return this[i];
		}

		#endregion
	}

	
	/// <summary>
	/// Comares 2 Scripts
	/// </summary>
	public class ScriptComparer
	{

		/// <summary>
		/// Compares the specified left.
		/// </summary>
		/// <param name="left">The left.</param>
		/// <param name="right">The right.</param>
		/// <returns></returns>
		public static ScriptComparisonResult Compare(Script left, Script right)
		{
			Script splittedLeft = SplitScriptToRows(left);
			Script splittedRight = SplitScriptToRows(right);
				
			ScriptComparisonResult compRes = new ScriptComparisonResult();
			bool areEqual = (splittedLeft != null) && (splittedRight != null) && (splittedLeft.Count == splittedRight.Count);
			int leftIndex = 0, rightIndex = 0;
			if (splittedLeft != null)
			{
				for (; leftIndex < splittedLeft.Count; leftIndex++, rightIndex++)
				{
					if (splittedRight != null && rightIndex < splittedRight.Count)
					{
						if (splittedLeft[leftIndex].InDBContextIsEqualTo(splittedRight[rightIndex]))
						{
							compRes.Add(new StringComparisonResult(splittedLeft[leftIndex], splittedRight[rightIndex], -1));
						}
						else
						{
							areEqual = false;
							int tempIndex;
							if ((tempIndex = CheckForInsertedStrings(splittedLeft, leftIndex, splittedRight, rightIndex)) != -1)
							{
								for (; rightIndex < tempIndex; rightIndex++)
									compRes.Add(new StringComparisonResult(null, splittedRight[rightIndex], -1));
								compRes.Add(new StringComparisonResult(splittedLeft[leftIndex], splittedRight[rightIndex], -1));
							}
							else if ((tempIndex = CheckForInsertedStrings(splittedRight, rightIndex, splittedLeft, leftIndex)) != -1)
							{
								for (; leftIndex < tempIndex; leftIndex++)
									compRes.Add(new StringComparisonResult(splittedLeft[leftIndex], null, -1));
								compRes.Add(new StringComparisonResult(splittedLeft[leftIndex], splittedRight[rightIndex], -1));
							}
							else
							{

								int diffIndex = FirstDifferenceIndex(splittedLeft[leftIndex], splittedRight[rightIndex], false);
								compRes.Add(new StringComparisonResult(splittedLeft[leftIndex], splittedRight[rightIndex], diffIndex));
							}
						}
					}
					else
					{
						compRes.Add(new StringComparisonResult(splittedLeft[leftIndex], null, -1));
					}
				}
			}
			if (splittedRight != null)
				for (; rightIndex < splittedRight.Count; rightIndex++)
				{
					compRes.Add(new StringComparisonResult(null, splittedRight[rightIndex], -1));
				}
			compRes.AreEqual = areEqual;
			return compRes;
		}


		private static int CheckForInsertedStrings(Script left, int leftStartIndex, Script right, int rightStartIndex)
		{
			for (int tempIndex = rightStartIndex; tempIndex < right.Count; tempIndex++)
			{
				if (left[leftStartIndex].InDBContextIsEqualTo(right[tempIndex]))
				{
					return tempIndex;
				}
			}
			return -1;
		}



		private static int FirstDifferenceIndex(string a, string b, bool ignoreCase)
		{
			var _a = (ignoreCase) ? a.ToLower() : a;
			var _b = (ignoreCase) ? b.ToLower() : b;

			int i = 0;
			for (; i < a.Length && i < b.Length; i++)
			{
				if (a[i] != b[i])
					return i;
			}
			return i;
		}

		private static Script SplitScriptToRows(Script scr)
		{
			if (scr == null)
				return null;
			Script ret = new Script();
			var querry = from row in scr
						 select row.Split("\r\n".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);

			foreach (string []s in querry)
				ret.AddRange(s);
			return ret;
		}
	}

	/// <summary>
	/// Extends the List of StringComparisonResult by adding the Are Equal property which shows
	/// whether the Scripts are equal
	/// </summary>
	public class ScriptComparisonResult : List<StringComparisonResult>
	{
		/// <summary>
		/// Gets or sets a value indicating whether [are equal].
		/// </summary>
		/// <value><c>true</c> if [are equal]; otherwise, <c>false</c>.</value>
		public bool AreEqual { get; internal set; }

		/// <summary>
		/// Gets the left side of the comparison.
		/// </summary>
		/// <value>The left.</value>
		public IEnumerable<string> Left
		{
			get
			{
				return this.Where(s => s.Left != null).Select(s => s.Left);

				//foreach (StringComparisonResult s in this)
				//{
				//    if (s != null)
				//        yield return s.Left;
				//}
			}
		}

		/// <summary>
		/// Gets the right side of the comparison.
		/// </summary>
		/// <value>The right.</value>
		public IEnumerable<string> Right
		{
			get
			{
				return this.Where(s => s.Right != null).Select(s => s.Right);
				//foreach (StringComparisonResult s in this)
				//{
				//    if (s != null)
				//        yield return s.Right;
				//}
			}
		}
	}


	/// <summary>
	/// Result of a ScriptComparer.Compare operation
	/// </summary>
	public class StringComparisonResult : IEquatable<StringComparisonResult>
	{
		/// <summary>
		/// Initializes a new instance of the <see cref="StringComparisonResult"/> class.
		/// </summary>
		/// <param name="left">The left.</param>
		/// <param name="right">The right.</param>
		/// <param name="diffIndex">Index of the diff.</param>
		public StringComparisonResult(string left, string right, int diffIndex)
		{
			Left = left;
			Right = right;
			DiffIndex = diffIndex;
		}

		/// <summary>
		/// Gets the index at which strings differ.
		/// </summary>
		/// <value>The index at which strings differ</value>
		public int DiffIndex { get; private set; }
		/// <summary>
		/// Gets or sets the left string
		/// </summary>
		/// <value>The left string</value>
		public string Left { get; private set; }
		/// <summary>
		/// Gets or sets the right string
		/// </summary>
		/// <value>The right string</value>
		public string Right { get; private set; }


		#region IEquatable<StringComparisonResult> Members

		/// <summary>
		/// Indicates whether the current object is equal to another object of the same type.
		/// </summary>
		/// <param name="other">An object to compare with this object.</param>
		/// <returns>
		/// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
		/// </returns>
		public bool Equals(StringComparisonResult other)
		{
			if ((StringComparer.Ordinal.Compare(Left, other.Left) == 0) &&
				(StringComparer.Ordinal.Compare(Right, other.Right) == 0) &&
				DiffIndex == other.DiffIndex)
				return true;
			return false;
		}

		/// <summary>
		/// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
		/// </summary>
		/// <returns>
		/// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
		/// </returns>
		public override string ToString()
		{
			return string.Format("Left: {0};Right: {1};DiffIndex: {2}", Left, Right, DiffIndex);
		}

		/// <summary>
		/// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
		/// </summary>
		/// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param>
		/// <returns>
		/// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false.
		/// </returns>
		/// <exception cref="T:System.NullReferenceException">The <paramref name="obj"/> parameter is null.</exception>
		public override bool Equals(object obj)
		{
			if (!(obj is StringComparisonResult))
				return false;
			else
				return Equals(obj as StringComparisonResult);

		}

		#endregion

		/// <summary>
		/// Serves as a hash function for a particular type.
		/// </summary>
		/// <returns>
		/// A hash code for the current <see cref="T:System.Object"/>.
		/// </returns>
		public override int GetHashCode()
		{
			return base.GetHashCode();
		}
	}

	/// <summary>
	/// Extends string class - converts string to a database compatible string.
	/// </summary>
	static class StringForDatabaseExtender
	{
		public static string AsDBString(this String a)
		{
			return a.Trim().Replace("\t", string.Empty);//.ToLowerInvariant()
		}

		public static bool InDBContextIsEqualTo(this string a, string b)
		{

			//if (string.CompareOrdinal(a.AsDBString(), b.AsDBString()) != 0)
			if (StringComparer.Ordinal.Compare(a.AsDBString(), b.AsDBString()) != 0)
				return false;
			return true;
		}

	}
}
